# !/usr/bin/env python
# -*-coding:utf-8 -*-
"""
# File       : app.py
# Time       ：2023/3/7 21:04
# Author     ：Y-aong
# version    ：python 3.7
# Description：orderlines app
"""
import uuid
from typing import List, Union

from apis.orderlines.schema.task_schema import TaskODSchema
from apis.orderlines.schema.variable_schema import VariableInfoSchema
from flask import Config

from apis.config.models import BaseConfig
from apis.orderlines.models import Process, Variable, VariableInstance, ProcessInstance, Task
from apis.orderlines.schema.process_schema import ProcessRunningSchema, ProcessInstanceSchema
from orderlines.execute.app_context import AppContext
from orderlines.execute.debug_runner import DebugRunner
from orderlines.execute.states.process_finite_state_machines import ProcessInstanceState
from orderlines.execute.task_runner import TaskRunner

from orderlines.process_build.process_build_adapter import ProcessBuildAdapter
from orderlines.utils.exceptions import OrderLinesRunningException
from orderlines.utils.orderlines_enum import TaskStates
from orderlines.utils.utils import default_task_config, clear_all_db

from public.base_model import session_scope
from public.logger import logger


class OrderLines:
    config_class = Config

    def __init__(self):
        self.process_build = ProcessBuildAdapter()
        self.context = AppContext()
        self.logger = logger

    def start_by_process_id(
            self,
            process_instance_id: str,
            process_id: Union[int, str],
            variable: List[dict] = None,
            run_type: str = 'trigger'
    ):
        """
        根据流程ID运行流程
        @param process_instance_id: 流程实例ID
        @param process_id: 流程ID
        @param variable: 流程参数
        @param run_type: 运行类型
        @return:
        """

        if isinstance(process_id, str):
            with session_scope() as session:
                obj = session.query(Process).filter(Process.process_id == process_id, Process.active == 1).first()
                process_info = ProcessRunningSchema().dump(obj)
        elif isinstance(process_id, int):
            with session_scope() as session:
                obj = session.query(Process).filter(Process.id == process_id, Process.active == 1).first()
                process_info = ProcessRunningSchema().dump(obj)
        else:
            raise ValueError(f'process id {process_id} type is not support. process id is only support int or str')
        if variable:
            # 修改现有的流程变量获取创建现有的流程变量
            self.insert_info_variable_instance(process_instance_id, variable)

        if process_info.get('process_id'):
            with session_scope() as session:
                task_objs = session.query(Task).filter(
                    Task.process_id == process_info.get('process_id'),
                    Task.active == 1,
                )
                task_nodes = TaskODSchema().dump(task_objs, many=True)
                return self._start(process_instance_id, process_info, task_nodes, run_type)
        else:
            raise OrderLinesRunningException(f'can not find task node by process id {process_id}')

    def start_by_file_path(self, process_instance_id, file_path: str, variable=None, run_type='trigger'):
        """
        根据流程文件运行流程，目前支持json,yaml文件
        @param process_instance_id: 流程实例ID
        @param file_path: 流程文件目前支持json,yaml文件
        @param variable: 流程参数
        @param run_type: 运行类型
        @return:
        """
        if file_path.endswith('json'):
            process_id = self.process_build.build_by_json(file_path)
        elif file_path.endswith('yaml'):
            process_id = self.process_build.build_by_yaml(file_path)
        else:
            raise ValueError('file type is not support. orderlines is only support json or yaml')

        return self.start_by_process_id(process_id, process_instance_id, variable, run_type)

    def start_by_dict(
            self,
            process_instance_id: str,
            process_info: dict,
            task_nodes: List[dict],
            variable: List[dict],
            run_type: str
    ):
        """
        根据字典运行流程
        @param process_instance_id:流程实例ID
        @param process_info:流程信息
        @param task_nodes:任务信息
        @param variable:流程变量
        @param run_type:运行类型
        @return:
        """
        process_id = self.process_build.build_by_dict(process_info, task_nodes, variable)
        return self.start_by_process_id(process_instance_id, process_id, variable, run_type)

    @staticmethod
    def insert_info_variable_instance(process_instance_id, variables: List[dict]):
        """
        默认流程变量插入流程变量实例表中
        @param process_instance_id:流程实例id
        @param variables: 变量列表
        @return:
        """
        if not variables:
            return
        for variable in variables:
            variable['process_instance_id'] = process_instance_id
            with session_scope() as session:
                obj = VariableInstance(**variable)
                session.add(obj)
                session.commit()

    def _start(self, process_instance_id, process_info: dict, task_nodes: List[dict], run_type):
        logger.info(f'process_instance_id::{process_instance_id}')
        process_info['process_instance_id'] = process_instance_id
        process_info['run_type'] = run_type
        for task_node in task_nodes:
            if not task_node.get('method_kwargs'):
                task_node['method_kwargs'] = {}
            task_config = task_node['task_config'] or []
            task_node['task_config'] = default_task_config(task_config)
            task_node['task_instance_id'] = uuid.uuid4().hex
        process_instance_info = {'process_info': process_info, 'task_nodes': task_nodes}
        self.context.setdefault(process_instance_id, process_instance_info)
        mode = process_info.get('mode')
        logger.info(f'process mode::{mode}')
        if mode == 'debug':
            DebugRunner(process_instance_id, self.context).debug()
        else:
            t = TaskRunner(process_instance_id, self.context)
            t.start()
        return process_instance_id

    def start(
            self,
            process_id: Union[None, int, str] = None,
            process_instance_id: str = None,
            file_path: str = None,
            process_info: dict = None,
            task_nodes: List[dict] = None,
            variable: List[dict] = None,
            run_type: str = 'trigger',
            clear_db: bool = False
    ) -> str:
        """
        启动流程
        start process
        @param process_id: process_id or process table id
        @param process_instance_id: process run instance id
        @param file_path: start by file ,now only support json or yaml
        @param process_info: process info base process table info
        @param task_nodes: task node one process can have many task node
        @param variable: process variable
        @param run_type: schedule or trigger
        @param clear_db: only test, if True will clear process ,process instance, task, task_instance
        @return:
        """
        if not process_instance_id:
            process_instance_id = uuid.uuid4().hex
        if clear_db:
            clear_all_db(process_id)

        if process_id and isinstance(process_id, str):
            with session_scope() as session:
                objs = session.query(Variable).filter(Variable.process_id == process_id, Variable.active == 1).all()
            variable_info = VariableInfoSchema().dump(objs, many=True)
            variable = variable_info if variable_info else []
        if process_id:
            return self.start_by_process_id(process_instance_id, process_id, variable, run_type)
        elif file_path:
            return self.start_by_file_path(process_instance_id, file_path, variable, run_type)
        else:
            return self.start_by_dict(process_instance_id, process_info, task_nodes, variable, run_type)

    def stop_process(self, process_instance_id: str, stop_schedule: bool = False):
        """stop process"""
        process_fsm = ProcessInstanceState(process_instance_id)
        process_fsm.trigger_chance('stop')

        if stop_schedule:
            self._stop_schedule(process_instance_id)

        # get current running task instance id
        from apis.orderlines.models import TaskInstance
        from apis.orderlines.schema.task_schema import TaskInstanceSchema
        with session_scope() as session:
            obj = session.query(TaskInstance).filter(TaskInstance.process_instance_id == process_instance_id).all()
            task_instance_info = TaskInstanceSchema().dump(obj, many=True)
            if task_instance_info:
                return [
                    item.get('task_instance_id')
                    for item in task_instance_info
                    if item.get('task_status') in [TaskStates.blue.value, TaskStates.grey.value]
                ]
            else:
                return []

    def paused_process(self, process_instance_id: str, stop_schedule: bool = False):
        """paused process"""

        process_fsm = ProcessInstanceState(process_instance_id)
        process_fsm.trigger_chance('pause')
        # stop schedule task
        if stop_schedule:
            self._stop_schedule(process_instance_id)

    @staticmethod
    def recover_process(process_instance_id: str, recover_schedule: bool = False):
        """recover process"""
        with session_scope() as session:
            obj = session.query(ProcessInstance).filter(
                ProcessInstance.process_instance_id == process_instance_id).first()

        process_instance_info = ProcessInstanceSchema().dump(obj)
        process_fsm = ProcessInstanceState(process_instance_id)
        process_fsm.trigger_chance('paused')

        if recover_schedule:
            from public.schedule_utils.apscheduler_utils import ApschedulerUtils
            ApschedulerUtils().recover_schedule_plan(process_instance_info.get('process_id'))

    @staticmethod
    def _stop_schedule(process_instance_id):
        """停止所有的定时任务"""
        with session_scope() as session:
            obj = session.query(ProcessInstance).filter(
                ProcessInstance.process_instance_id == process_instance_id).first()
        process_instance_info = ProcessInstanceSchema().dump(obj)
        from public.schedule_utils.apscheduler_utils import ApschedulerUtils
        ApschedulerUtils().paused_schedule_plan(process_instance_info.get('process_id'))

    @staticmethod
    def stop_all_schedule():
        """stop all schedule task"""
        with session_scope() as session:
            session.query(BaseConfig).filter(
                BaseConfig.config_name == 'stop_all_schedule').update({'config_value': '1'})

    @staticmethod
    def recover_all_schedule():
        """recover all schedule"""
        with session_scope() as session:
            session.query(BaseConfig).filter(
                BaseConfig.config_name == 'stop_all_schedule').update({'config_value': '0'})
